home *** CD-ROM | disk | FTP | other *** search
/ Supercompiler 1997 / SUPERCOMPILER97.iso / Delphi 3.0 / DATA.Z / ibctrls.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-01-30  |  13.5 KB  |  478 lines

  1. {********************************************************}
  2. {                                                        }
  3. {       InterBase EventAlerter components                }
  4. {       Copyright (c) 1995 Borland International         }
  5. {                                                        }
  6. {       Written by:                                      }
  7. {         James Thorpe                                   }
  8. {         CSA Australasia                                }
  9. {         Compuserve: 100035,2064                        }
  10. {         Internet:   csa@csaa.com.au                    }
  11. {                                                        }
  12. {********************************************************}
  13.  
  14. unit IBCtrls;
  15.  
  16. interface
  17.  
  18. uses
  19.   SysUtils, Windows, Messages, Classes, Graphics, Controls,
  20.   Forms, Dialogs, DB, DBTables, IBProc32, BDE;
  21.  
  22. const
  23.   MaxEvents = 15;
  24.   EventLength = 64;
  25.  
  26. type
  27.  
  28.   TIBComponent = class( TComponent)
  29.   private
  30.     FDatabase: TDatabase;
  31.     procedure SetDatabase( value: TDatabase);
  32.     procedure ValidateDatabase( Database: TDatabase);
  33.   protected
  34.     function  GetNativeHandle: isc_db_handle;
  35.     procedure HandleIBErrors( status: pstatus_vector);
  36.     function  IsInterbaseDatabase( Database: TDatabase): Boolean;
  37.   published
  38.     property  Database: TDatabase read FDatabase write SetDatabase;
  39.   end;
  40.  
  41.   TEventAlert = procedure( Sender: TObject; EventName: string; EventCount: longint;
  42.                            var CancelAlerts: Boolean) of object;
  43.  
  44.   TEventBuffer = array[ 0..MaxEvents-1, 0..EventLength-1] of char;
  45.  
  46.   TIBEventAlerter = class(TIBComponent)
  47.   private
  48.     LibHandle: THandle;
  49.     FEvents: TStrings;
  50.     FOnEventAlert: TEventAlert;
  51.     FQueued: Boolean;
  52.     FRegistered: Boolean;
  53.     Buffer: TEventBuffer;
  54.     Changing: Boolean;
  55.     CS: TRTLCriticalSection;
  56.     EventBuffer: PChar;
  57.     EventBufferLen: integer;
  58.     EventID: isc_long;
  59.     ProcessingEvents: Boolean;
  60.     RegisteredState: Boolean;
  61.     ResultBuffer: PChar;
  62.     procedure DoQueueEvents;
  63.     procedure EventChange( sender: TObject);
  64.     procedure UpdateResultBuffer( length: short; updated: PChar);
  65.   protected
  66.     procedure HandleEvent;
  67.     procedure Loaded; override;
  68.     procedure Notification( AComponent: TComponent; Operation: TOperation); override;
  69.     procedure SetEvents( value: TStrings);
  70.     procedure SetDatabase( value: TDatabase);
  71.     procedure SetRegistered( value: boolean);
  72.   public
  73.     constructor Create( AOwner: TComponent); override;
  74.     destructor Destroy; override;
  75.     procedure CancelEvents;
  76.     procedure QueueEvents;
  77.     procedure RegisterEvents;
  78.     procedure UnRegisterEvents;
  79.     property  Queued: Boolean read FQueued;
  80.   published
  81.     property Events: TStrings read FEvents write SetEvents;
  82.     property Registered: Boolean read FRegistered write SetRegistered;
  83.     property OnEventAlert: TEventAlert read FOnEventAlert write FOnEventAlert;
  84.   end;
  85.  
  86.   EIBError = class( Exception);
  87.  
  88. implementation
  89.  
  90. {$R *.RES}
  91.  
  92. const
  93.   SIBMessageBase       = 57800;
  94.   SNoEventsRegistered  = SIBMessageBase + 0;
  95.   SInvalidDBConnection = SIBMessageBase + 1;
  96.   SInvalidDatabase     = SIBMessageBase + 2;
  97.   SInvalidCancellation = SIBMessageBase + 3;
  98.   SInvalidEvent        = SIBMessageBase + 4;
  99.   SInvalidQueueing     = SIBMessageBase + 5;
  100.   SInvalidRegistration = SIBMessageBase + 6;
  101.   SMaximumEvents       = SIBMessageBase + 7;
  102.  
  103. var
  104.   // Dynamically Loaded InterBase API functions (gds32.dll)
  105.   IscQueEvents: TIscQueEvents;
  106.   IscFree: TIscfree;
  107.   IscEventBlock: TIscEventBlock;
  108.   IscEventCounts: TIscEventCounts;
  109.   IscCancelEvents: TIscCancelEvents;
  110.   IscInterprete: TIscInterprete;
  111.  
  112.  
  113. // TIBComponent
  114.  
  115. function TIBComponent.GetNativeHandle: isc_db_handle;
  116. var
  117.   length: word;
  118. begin
  119.   if assigned( FDatabase) and FDatabase.Connected then
  120.     Check( DbiGetProp( HDBIOBJ(FDatabase.Handle), dbNATIVEHNDL,
  121.                        @result, sizeof( isc_db_handle), length))
  122.   else result := nil;
  123. end;
  124.  
  125. procedure TIBComponent.HandleIBErrors( status: pstatus_vector);
  126. var
  127.   buffer: array[0..255] of char;
  128.   errMsg, lastMsg: string;
  129.   errCode: isc_status;
  130. begin
  131.   errMsg := '';
  132.   repeat
  133.     errCode := IscInterprete( @buffer, @status);
  134.     if lastMsg <> strPas( Buffer) then
  135.     begin
  136.       lastMsg := strPas( buffer);
  137.       if length( errMsg) <> 0 then errMsg := errMsg+#13#10;
  138.       errMsg := errMsg+lastMsg;
  139.     end;
  140.   until errCode = 0;
  141.   raise EIBError.Create( errMsg);
  142. end;
  143.  
  144. function TIBComponent.IsInterbaseDatabase( Database: TDatabase): Boolean;
  145. var
  146.   Length: Word;
  147.   Buffer: array[0..63] of Char;
  148. begin
  149.   Result := False;
  150.   if Database.Handle <> nil then
  151.   begin
  152.     Check(DbiGetProp(HDBIOBJ(Database.Handle), dbDATABASETYPE, @Buffer,
  153.       SizeOf(Buffer), Length));
  154.     Result := StrIComp(Buffer, 'INTRBASE') = 0;
  155.   end;
  156. end;
  157.  
  158. procedure TIBComponent.SetDatabase( value: TDatabase);
  159. begin
  160.   if value <> FDatabase then
  161.   begin
  162.     if assigned( value) and value.Connected then ValidateDatabase( value);
  163.     FDatabase := value;
  164.   end;
  165. end;
  166.  
  167. procedure TIBComponent.ValidateDatabase( Database: TDatabase);
  168. begin
  169.   if not assigned( Database) or not Database.Connected then
  170.     raise EIBError.CreateRes( SInvalidDBConnection)
  171.   else if not IsInterbaseDatabase( Database) then
  172.     raise EIBError.CreateResFmt( SInvalidDatabase, [Database.Name]);
  173. end;
  174.  
  175. // TIBEventAlerter
  176.  
  177. procedure HandleEvent( param: integer); stdcall;
  178. begin
  179.   // don't let exceptions propogate out of thread
  180.   try
  181.     TIBEventAlerter( param).HandleEvent;
  182.   except
  183.     Application.HandleException( nil);
  184.   end;
  185. end;
  186.  
  187. procedure IBEventCallback( ptr: pointer; length: short; updated: PChar); cdecl;
  188. var
  189.   ThreadID: integer;
  190. begin
  191.   // Handle events asynchronously in second thread
  192.   EnterCriticalSection( TIBEventAlerter( ptr).CS);
  193.   TIBEventAlerter( ptr).UpdateResultBuffer( length, updated);
  194.   CloseHandle( CreateThread( nil, 8192, @HandleEvent, ptr, 0, ThreadID));
  195.   LeaveCriticalSection( TIBEventAlerter( ptr).CS);
  196. end;
  197.  
  198. constructor TIBEventAlerter.Create( AOwner: TComponent);
  199. begin
  200.   inherited Create( AOwner);
  201.   InitializeCriticalSection( CS);
  202.   FEvents := TStringList.Create;
  203.   with TStringList( FEvents) do
  204.   begin
  205.     OnChange := EventChange;
  206.     Duplicates := dupIgnore;
  207.   end;
  208.   // Attempt to load GDS32.DLL.  If this fails then raise an exception.
  209.   // This will cause the component not to be created
  210.   LibHandle := LoadLibrary('gds32.dll');
  211.   if LibHandle < 32 then
  212.     raise EDLLLoadError.Create('Unable to load gds32.dll');
  213.  
  214.   @IscQueEvents := GetProcAddress(LibHandle, 'isc_que_events');
  215.   if @IscQueEvents = nil then
  216.     raise EDLLLoadError.Create('Failed to lookup isc_que_events');
  217.  
  218.   @IscInterprete := GetProcAddress(LibHandle, 'isc_interprete');
  219.   if @IscInterprete = nil then
  220.     raise EDLLLoadError.Create('Failed to lookup isc_interprete');
  221.  
  222.   @IscFree := GetProcAddress(LibHandle, 'isc_free');
  223.   if @IscFree = nil then
  224.     raise EDLLLoadError.Create('Failed to lookup isc_free');
  225.  
  226.   @IscEventBlock := GetProcAddress(LibHandle, 'isc_event_block');
  227.   if @IscEventBlock = nil then
  228.     raise EDLLLoadError.Create('Failed to lookup isc_event_block');
  229.  
  230.   @IscEventCounts := GetProcAddress(LibHandle, 'isc_event_counts');
  231.   if @IscEventCounts = nil then
  232.     raise EDLLLoadError.Create('Failed to lookup isc_event_counts');
  233.  
  234.   @IscCancelEvents := GetProcAddress(LibHandle, 'isc_cancel_events');
  235.   if @IscCancelEvents = nil then
  236.     raise EDLLLoadError.Create('Failed to lookup isc_cancel_events');
  237.  
  238. end;
  239.  
  240. destructor TIBEventAlerter.Destroy;
  241. begin
  242.   UnregisterEvents;
  243.   SetDatabase( nil);
  244.   TStringList(FEvents).OnChange := nil;
  245.   FEvents.Free;
  246.   DeleteCriticalSection( CS);
  247.   inherited Destroy;
  248.   if LibHandle >= 32 then
  249.     FreeLibrary(LibHandle);
  250.  
  251. end;
  252.  
  253. procedure TIBEventAlerter.CancelEvents;
  254. var
  255.   status: status_vector;
  256.   errCode: isc_status;
  257.   dbHandle: isc_db_handle;
  258. begin
  259.   if ProcessingEvents then
  260.     raise EIBError.CreateRes( SInvalidCancellation);
  261.   if FQueued then
  262.   begin
  263.     try
  264.       // wait for event handler to finish before cancelling events
  265.       EnterCriticalSection( CS);
  266.       ValidateDatabase( Database);
  267.       FQueued := false;
  268.       Changing := true;
  269.       dbHandle := GetNativeHandle;
  270.       errCode := IscCancelEvents( @status, @dbHandle, @EventID);
  271.       if errCode <> 0 then HandleIBErrors( @status)
  272.     finally
  273.       LeaveCriticalSection( CS);
  274.     end;
  275.   end;
  276. end;
  277.  
  278. procedure TIBEventAlerter.DoQueueEvents;
  279. var
  280.   status: status_vector;
  281.   errCode: isc_status;
  282.   callback: pointer;
  283.   dbHandle: isc_db_handle;
  284. begin
  285.   ValidateDatabase( DataBase);
  286.   callback := @IBEventCallback;
  287.   dbHandle := GetNativeHandle;
  288.   errCode := IscQueEvents( @status, @dbHandle, @EventID, EventBufferLen,
  289.                                EventBuffer, isc_callback(callback), self);
  290.   if errCode <> 0 then HandleIBErrors( @status);
  291.   FQueued := true;
  292. end;
  293.  
  294. procedure TIBEventAlerter.EventChange( sender: TObject);
  295. begin
  296.   // check for blank event
  297.   if TStringList(Events).IndexOf( '') <> -1 then
  298.     raise EIBError.CreateRes( SInvalidEvent);
  299.   // check for too many events
  300.   if Events.Count > MaxEvents then
  301.   begin
  302.     TStringList(Events).OnChange := nil;
  303.     Events.Delete( MaxEvents);
  304.     TStringList(Events).OnChange := EventChange;
  305.     raise EIBError.CreateRes( SMaximumEvents);
  306.   end;
  307.   if Registered then RegisterEvents;
  308. end;
  309.  
  310. procedure TIBEventAlerter.HandleEvent;
  311. var
  312.   CancelAlerts: Boolean;
  313.   i: integer;
  314.   status: status_vector;
  315. begin
  316.   try
  317.     // prevent modification of vital data structures while handling events
  318.     EnterCriticalSection( CS);
  319.     ProcessingEvents := true;
  320.     IscEventCounts( @status, EventBufferLen, EventBuffer, ResultBuffer);
  321.     CancelAlerts := false;
  322.     if assigned(FOnEventAlert) and not Changing then
  323.     begin
  324.       for i := 0 to Events.Count-1 do
  325.       begin
  326.         try
  327.           if (status[i] <> 0) and not CancelAlerts then
  328.             FOnEventAlert( self, Events[Events.Count-i-1], status[i], CancelAlerts);
  329.         except
  330.           Application.HandleException( nil);
  331.         end;
  332.       end;
  333.     end;
  334.     Changing := false;
  335.     if not CancelAlerts and FQueued then DoQueueEvents;
  336.   finally
  337.     ProcessingEvents := false;
  338.     LeaveCriticalSection( CS);
  339.   end;
  340. end;
  341.  
  342. procedure TIBEventAlerter.Loaded;
  343. begin
  344.   inherited Loaded;
  345.   try
  346.     if RegisteredState then RegisterEvents;
  347.   except
  348.     if csDesigning in ComponentState then
  349.       Application.HandleException( self)
  350.     else raise;
  351.   end;
  352. end;
  353.  
  354. procedure TIBEventAlerter.Notification( AComponent: TComponent;
  355.                                         Operation: TOperation);
  356. begin
  357.   inherited Notification( AComponent, Operation);
  358.   if (Operation = opRemove) and (AComponent = FDatabase) then
  359.   begin
  360.     UnregisterEvents;
  361.     FDatabase := nil;
  362.   end;
  363. end;
  364.  
  365. procedure TIBEventAlerter.QueueEvents;
  366. begin
  367.   if not FRegistered then
  368.     raise EIBError.CreateRes( SNoEventsRegistered);
  369.   if ProcessingEvents then
  370.     raise EIBError.CreateRes( SInvalidQueueing);
  371.   if not FQueued then
  372.   begin
  373.     try
  374.       // wait until current event handler is finished before queuing events
  375.       EnterCriticalSection( CS);
  376.       DoQueueEvents;
  377.       Changing := true;
  378.     finally
  379.       LeaveCriticalSection( CS);
  380.     end;
  381.   end;
  382. end;
  383.  
  384. procedure TIBEventAlerter.RegisterEvents;
  385. var
  386.   i: integer;
  387.   bufptr: pointer;
  388.   eventbufptr: pointer;
  389.   resultbufptr: pointer;
  390.   buflen: integer;
  391. begin
  392.   ValidateDatabase( Database);
  393.   if csDesigning in ComponentState then FRegistered := true
  394.   else begin
  395.     UnregisterEvents;
  396.     if Events.Count = 0 then exit;
  397.     for i := 0 to Events.Count-1 do
  398.       StrPCopy( @Buffer[i][0], Events[i]);
  399.     i := Events.Count;
  400.     bufptr := @buffer[0];
  401.     eventbufptr :=  @EventBuffer;
  402.     resultBufPtr := @ResultBuffer;
  403.     asm
  404.       mov ecx, dword ptr [i]
  405.       mov eax, dword ptr [bufptr]
  406.       @@1:
  407.       push eax
  408.       add  eax, EventLength
  409.       loop @@1
  410.       push dword ptr [i]
  411.       push dword ptr [resultBufPtr]
  412.       push dword ptr [eventBufPtr]
  413.       call [IscEventBlock]
  414.       mov  dword ptr [bufLen], eax
  415.       mov eax, dword ptr [i]
  416.       shl eax, 2
  417.       add eax, 12
  418.       add esp, eax
  419.     end;
  420.     EventBufferlen := Buflen;
  421.     FRegistered := true;
  422.     QueueEvents;
  423.   end;
  424. end;
  425.  
  426. procedure TIBEventAlerter.SetEvents( value: TStrings);
  427. begin
  428.   FEvents.Assign( value);
  429. end;
  430.  
  431. procedure TIBEventAlerter.SetDatabase( value: TDatabase);
  432. begin
  433.   if value <> FDatabase then
  434.   begin
  435.     UnregisterEvents;
  436.     if assigned( value) and value.Connected then ValidateDatabase( value);
  437.     FDatabase := value;
  438.   end;
  439. end;
  440.  
  441. procedure TIBEventAlerter.SetRegistered( value: Boolean);
  442. begin
  443.   if (csReading in ComponentState) then
  444.     RegisteredState := value
  445.   else if FRegistered <> value then
  446.     if value then RegisterEvents else UnregisterEvents;
  447. end;
  448.  
  449. procedure TIBEventAlerter.UnregisterEvents;
  450. begin
  451.   if ProcessingEvents then
  452.     raise EIBError.CreateRes( SInvalidRegistration);
  453.   if csDesigning in ComponentState then
  454.     FRegistered := false
  455.   else if not (csLoading in ComponentState) then
  456.   begin
  457.     CancelEvents;
  458.     if FRegistered then
  459.     begin
  460.       IscFree( EventBuffer);
  461.       EventBuffer := nil;
  462.       IscFree( ResultBuffer);
  463.       ResultBuffer := nil;
  464.     end;
  465.     FRegistered := false;
  466.   end;
  467. end;
  468.  
  469. procedure TIBEventAlerter.UpdateResultBuffer( length: short; updated: PChar);
  470. var
  471.   i: integer;
  472. begin
  473.   for i := 0 to length-1 do
  474.     ResultBuffer[i] := updated[i];
  475. end;
  476.  
  477. end.
  478.